/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-SpringBoot组件封装-缺省实现
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.web.servlets;

//import com.google.common.collect.HashMultimap;
//import com.google.common.collect.Multimap;
//import com.google.common.collect.Multimaps;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.taipingframework.boot.web.support.WebMvcHelper;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
//import org.springframework.http.MediaType;

import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 自定义任务对象
 */
@Slf4j
@Getter
@Setter
@Builder
@ToString
@EqualsAndHashCode
class AsyncTask {

    private static Integer responseTimeout;

    private static LinkedBlockingQueue<AsyncTask> asyncTaskQueue;
    // private Multimap<String, AsyncTask> servletRequestContext = Multimaps.synchronizedSetMultimap(HashMultimap.create());
    private static ScheduledExecutorService timeoutChecker;
    private static ExecutorService asyncExecutor;

    static {
        AsyncTask.asyncTaskQueue = new LinkedBlockingQueue<>();
        AsyncTask.timeoutChecker = Executors.newScheduledThreadPool(1,
                new ThreadFactoryBuilder().setNameFormat("zxb-async-handler%s").build());
    }

    /**
     * 长轮询请求的上下文，包含请求和响应体
     */
    private AsyncContext asyncContext;
    /**
     * 超时标记
     */
    private Boolean hasTimeout;
    /**
     * 请求方法
     */
    private HttpMethod httpMethod;
    /**
     * 资源标识
     * (理解为spring-mvc中的api-path，而注解@WebServlet的参数urlPatterns可理解为spring-mvc中的context-path)
     */
    private String name;

    static void setInitParamMap(ConcurrentHashMap<String, String> initParamMap) {
        AsyncTask.responseTimeout = Integer.valueOf(initParamMap.get("timeout"));
    }

    static void setAsyncExecutor(ExecutorService asyncExecutor) {
        AsyncTask.asyncExecutor = asyncExecutor;
    }

    /**
     * 创建任务处理器
     */
    static Thread createTaskHandler() {
        return new Thread(() -> {
            while (true) {
                try {
                    AsyncTask asyncTask = asyncTaskQueue.take(); // 阻塞等待，消费队列中的任务
                    String service = asyncTask.getName();
                    asyncExecutor.execute(() -> {
                        if ("/get".equalsIgnoreCase(service)) {
                            AsyncTask.requestHandler(asyncTask, HttpStatus.OK, "get action");
                        } else if ("/post".equalsIgnoreCase(service)) {
                            AsyncTask.requestHandler(asyncTask, HttpStatus.OK, "post action");
                        } else if ("/put".equalsIgnoreCase(service)) {
                            AsyncTask.requestHandler(asyncTask, HttpStatus.OK, "put action");
                        } else if ("/delete".equalsIgnoreCase(service)) {
                            AsyncTask.requestHandler(asyncTask, HttpStatus.OK, "delete action");
                        } else if ("/timeout".equalsIgnoreCase(service)) {
                            try {
                                TimeUnit.MILLISECONDS.sleep(AsyncTask.responseTimeout + AsyncTask.responseTimeout); // 模拟超时的API操作
                            } catch (Exception ex) {
                                log.error(ex.getMessage(), ex);
                            }
                        } else {
                            AsyncTask.requestHandler(asyncTask, HttpStatus.OK, "unknown mapping");
                        }
                    });
                } catch (InterruptedException ex) {
                    log.error(ex.getMessage(), ex);
                }
            }
        });
    }

    /**
     * 延迟处理器
     */
    static void delayHandler(HttpServletRequest req, HttpServletResponse resp, HttpMethod httpMethod) {
        AsyncContext context = req.startAsync(req, resp);
        context.setTimeout(responseTimeout);
        String service = req.getRequestURI().replaceFirst(req.getServletPath(), StringUtils.EMPTY);
        AsyncTask asyncTask = AsyncTask.builder()
                .asyncContext(context)
                .httpMethod(httpMethod)
                .hasTimeout(true)
                .name(service)
                .build();

        // servletRequestContext.put(service, asyncTask);
        try {
            asyncTaskQueue.put(asyncTask); // 阻塞等待，向队列中发布任务
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }

        timeoutChecker.schedule(() -> {
            // 触发定时后，判断任务是否被执行，即hasTimeout为true（没有被执行）
            // 则返回客户端304的状态码-即无修改。
            if (asyncTask.getHasTimeout()) {
                // 清除缓存中的任务
                asyncTaskQueue.remove(asyncTask);
                //if (servletRequestContext.remove(service, asyncTask)) {
                AsyncTask.requestHandler(asyncTask, HttpStatus.REQUEST_TIMEOUT, "请求超时");
                //}
            }
        }, AsyncTask.responseTimeout, TimeUnit.MILLISECONDS);
    }

    /**
     * 异步请求处理器
     */
    private static void requestHandler(AsyncTask asyncTask, HttpStatus httpStatus, String msg) {
        try {
            AsyncContext context = asyncTask.getAsyncContext();

            HttpServletResponse resp = (HttpServletResponse) context.getResponse();
            resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
            //resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
            resp.setStatus(httpStatus.value());
            resp.getWriter().write(msg);

            asyncTask.setHasTimeout(false);
            context.complete();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            // 清理当前HTTP请求线程缓存
            WebMvcHelper.clearThreadCache();
        }
    }

}
